#!/usr/bin/perl

package MozComics::Utils;

use strict;
use warnings;
use DateTime;

# all columns in the strip table (automatically filled columns listed first)
use constant ALL_COLUMNS => qw(comic updated strip title url image bookmark
    extra action);

# columns that scripts using this package are responsible for
use constant MANUAL_COLUMNS => qw(strip title url image bookmark extra action);
use constant MANUAL_DEFAULTS => (0, '', '', '', 0, '', 0); #NOTE: strip required

# includes only manual columns
use constant INT_COLUMNS => qw(strip bookmark action);
use constant STRING_COLUMNS => qw(title url image extra);

# code needed when running on MozComics server in order to use some perl modules
# should not affect code running outside of MozComics server
my $homedir = ( getpwuid($>) )[7];
my @user_include;
foreach my $path (@INC) {
	if ( -d $homedir . '/perl' . $path ) {
		push @user_include, $homedir . '/perl' . $path;
	}
} 
unshift @INC, @user_include;


sub getName {
	my $self = shift(@_);
	return $self->{name};	
}


sub scrapeUnique {
	my $self = shift(@_);
	my $errorHeader = "MozComics->scrapeUnique ($self->{name})";

	my $page = shift(@_) or die "$errorHeader: page source not passed";
	my $regex = shift(@_) or die "$errorHeader: regex not passed";
	$errorHeader .= " (regex = $regex)";

	my @nonGlobal = ($page =~ /$regex/);
	my $nonGlobalSize = @nonGlobal;
	if($nonGlobalSize == 0) {
		die "$errorHeader: no matches found";
	}

	my @global = ($page =~ /$regex/g);
	my $globalSize = @global;
	if($nonGlobalSize != $globalSize) {
		die "$errorHeader: match found, but not unique";
	}

	return @nonGlobal;
}


sub assertStripsEqual {
	my $self = shift(@_);
	my $errorHeader = "MozComics->assertStripsEqual ($self->{name})";

	my $a = shift(@_) or die "$errorHeader: first strip not passed";
	my $b = shift(@_) or die "$errorHeader: second strip not passed";

	# check to see if contents of int columns are equal
	for my $col (INT_COLUMNS) {
		if((defined $a->{$col}) and (defined $b->{$col})) {
			if($col eq 'strip') {
				my $aDate = DateTime->from_epoch(epoch => $a->{$col});
				my $bDate = DateTime->from_epoch(epoch => $b->{$col});
				if($aDate->ymd ne $bDate->ymd) {
					die "$errorHeader: '$col' translate to different dates";
				}
				next;
			}

			if($a->{$col} != $b->{$col}) {
				die "$errorHeader: '$col' values are not equal";
			}
		}
	}

	# check to see if contents of string columns are equal
	for my $col (STRING_COLUMNS) {
		if((defined $a->{$col}) and (defined $b->{$col})) {
			if($a->{$col} ne $b->{$col}) {
				die "$errorHeader: '$col' values are not equal";
			}
		}
	}
}


sub addStrip {
	my $self = shift(@_);
	my $errorHeader = "MozComics->addStrip ($self->{name})";

	if($self->{committed}) {
		die "$errorHeader: can not add strips after committing";
	}

	my $queueSize = @{$self->{stripQueue}};
	# check to see if adding another strip would exceed maxNew size limit
	if($queueSize >= $self->{maxNew}) {
		die "$errorHeader: attempted to add more than max add size limit";
	}

	my $strip = shift(@_) or die "$errorHeader: strip not passed";

	# Fill in missing strip propery values with defaults
	my $i = -1;
	for my $col (MANUAL_COLUMNS) {
		$i++;

		if($col eq 'strip') {
			if(not defined $strip->{$col}) {
				die "$errorHeader: missing '$col' value";
			}
			next;
		}

		if(not defined $strip->{$col}) {
			$strip->{$col} = (MANUAL_DEFAULTS)[$i];
		}
	}

	push(@{$self->{stripQueue}}, $strip);
}

1;

__END__

=head1 NAME

MozComics::Utils - Methods available when using a MozComics comic object, independent of the back-end

=head1 SYNOPSIS

	use MozComics::JSON;
	use MozComics::MySQL;

	$comic = MozComics::JSON->new($guid, $maxNewStrips);
	$comic = MozComics::MySQL->new($guid, $maxNewStrips);

	$comic->addStrip($strip);
	$comic->commit();

	$comicName = $comic->getName();
	$lastStrip = $comic->getLastStrip();
	$stripCount = $comic->getStripCount($conditions);

	@scraped = $comic->scrapeUnique($testString, $regex);

	$comic->assertStripsEqual($strip1, $strip2);

=head1 DESCRIPTION

MozComics::Utils represents the methods available when using a MozComics comic object. Both the JSON back-end and the MySQL back-end provide the same exact methods. Therefore, in order to switch between the two, only the C<new()> line needs to be changed in the scraper. For a JSON back-end, use MozComics::JSON->new(...), and for a MySQL back-end, use MozComics::MySQL->new(...).

=head2 Methods

=over 2

=item * C<new($guid [, $maxNewStrips])>

Create a new comic object. The first argument is the GUID of the comic (e.g. "00000000-1111-2222-3333-444444444444"). The second argument is the maximum number of new strips that can be added on commit. If a scraper tries to add more than this amount of strips, it will die. This ensures, for example, that a bug in a scraper won't cause the addition of a bunch of previously added strips. This argument is optional, and defaults to 1.

=item * C<addStrip($strip)>

Add a strip to the queue, so it is added to the back-end on commit. The first argument is the strip to be added as a hash reference. Dies if no argument passed, if the comic object was already committed, or if the strip addition breaks the max new strips bound. Note that nothing in the back-end is changed when addStrip is called. Only when commit is called does the back-end change.

=item * C<commit()>

Commit strips in queue to back-end, and update the comic's 'updated' column. Dies if the comic object was previously committed.

=item * C<getName()>

Get the name of the comic.

=item * C<getLastStrip()>

Get the last strip of a comic as a hash reference, where "last" is based on the 'strip' value of the strips. Dies if no strips were found.

=item * C<getStripCount([$conditions])>

Get the number of strips satisfying certain conditions. The first argument is a hash reference of the conditions to check. This method counts the number of strips that satisfy every condition in the hash reference. This method only supports equality conditions. For example, C<getStripCount({action =E<gt> 0, bookmark =E<gt> 1})> returns the number of strips whose 'action' value is 0 (i.e. "new" strip) and whose 'bookmark' value is 1 (i.e. strip is the start of a chapter). If no argument is passed, then conditions defaults to {}, and thus will return the total number of strips for the comic, including the number of "deleted" and "updated" strips.

Note that this method is severely limited and is only supposed to be useful in rare cases.

=item * C<scrapeUnique($testString, $regex)>

Find a regex match and ensure it is unique. Returns an array containing the results of the unique match. Dies if no match was found, or if multiple matches were found. The first argument is the string to test. The second argument is the regex to execute. This method is used to enforce a stricter scraping in order to prevent incorrect scrapings.

=item * C<assertStripsEqual($strip1, $strip2)>

Assert two strips are equal. Equality is true if all strip parameters are exactly equal, except for the 'strip' parameter, which only tests to see if they represent the same day. If one of the parameters is not defined for one of the strips, then it is not tested for equality (i.e. only test parameters that are defined for both passed strips). Dies if two strips are not passed or if the two passed strips are not equal.

=back

=head1 AUTHOR

Ben Parr <bparr@bparr.com>

=head1 COPYRIGHT

Copyright (c) 2009-2010 Ben Parr. All code licensed under the The MIT License (L<http://www.opensource.org/licenses/mit-license.php>)

=head1 SEE ALSO

L<MozComics::MySQL>, L<MozComics::JSON>

=cut

